package com.ymx.darling.spring;

import com.ymx.darling.build.Configuration;
import com.ymx.darling.build.mapper.MapperMethodInfoInfoManager;
import com.ymx.darling.cursor.Cursor;
import com.ymx.darling.dialect.page.Page;
import com.ymx.darling.exception.DarlingException;
import com.ymx.darling.exception.MapperException;
import com.ymx.darling.mapper.MapperMethodInfoRepository;
import com.ymx.darling.mapper.proxy.DarlingProxyFactory;
import com.ymx.darling.mapper.proxy.MapperProxyInvocationHandler;
import com.ymx.darling.session.Session;
import com.ymx.darling.session.SessionFactory;
import com.ymx.darling.session.setting.Callable;
import com.ymx.darling.spring.exception.DarlingPersistenceExceptionTranslator;
import com.ymx.darling.transaction.TransactionManager;
import com.ymx.darling.util.ExceptionUtil;
import org.springframework.beans.factory.DisposableBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

/**
 * session模板
 * @author 爱Java的小于
 */
public class SessionTemplate implements Session, DisposableBean {
    private final DarlingPersistenceExceptionTranslator darlingPersistenceExceptionTranslator;
    private final SessionFactory sessionFactory;
    private final Session sessionProxy;
    
    public SessionTemplate(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        TransactionManager transactionFactory = sessionFactory.getConfiguration().getTransactionFactory();
        this.darlingPersistenceExceptionTranslator = new DarlingPersistenceExceptionTranslator(transactionFactory.getDataSource(), true);
        this.sessionProxy = DarlingProxyFactory.newInstance(Session.class, new SessionInvocationHandler());
    }

    @Override
    public <T> T selectOne(Class<?> aClass, String s, Object... objects) {
        return this.sessionProxy.selectOne(aClass, s, objects);
    }

    @Override
    public <T> List<T> selectList(Class<?> aClass, String s, Object... objects) {
        return this.sessionProxy.selectList(aClass, s, objects);
    }

    @Override
    public <K, T> Map<K, T> selectMap(Class<?> aClass, String s, String s1, Object... objects) {
        return this.sessionProxy.selectMap(aClass, s, s1, objects);
    }

    @Override
    public <T> Cursor<T> selectCursor(Class<?> aClass, int i, String s, Object... objects) {
        return this.sessionProxy.selectCursor(aClass, i, s, objects);
    }

    @Override
    public <T> Page<T> selectPage(Class<?> aClass, String s, Object... objects) {
        return this.sessionProxy.selectPage(aClass, s, objects);
    }

    @Override
    public int update(String s, Object... objects) {
        return this.sessionProxy.update(s, objects);
    }

    @Override
    public <T> T getMapper(Class<T> mapperClass) {
        if (!MapperMethodInfoInfoManager.hasMapper(mapperClass)) {
            throw new MapperException("接口:"+mapperClass+"没有被注册");
        }

        MapperMethodInfoInfoManager mapperMethodInfoManager = this.getConfiguration().getMapperMethodInfoManager();
        MapperInvocationHandler handler = new MapperInvocationHandler(this, mapperMethodInfoManager, mapperClass);
        return DarlingProxyFactory.newInstance(mapperClass, handler);
    }

    @Override
    public Configuration getConfiguration() {
        return this.sessionFactory.getConfiguration();
    }

    @Override
    public void commit() {
        throw new UnsupportedOperationException("spring管理事务不允许手动提交");
    }

    @Override
    public void rollBack() {
        throw new UnsupportedOperationException("spring管理事务不允许手动回滚");
    }

    @Override
    public Connection getConnection() {
        return this.sessionProxy.getConnection();
    }

    @Override
    public void close() {
        throw new UnsupportedOperationException("spring管理事务不允许手动关闭");
    }

    @Override
    public void destroy() {
    }

    @Override
    public int updateCall(Callable callable) {
        return this.sessionProxy.updateCall(callable);
    }

    @Override
    public <T> List<T> selectCall(Callable callable) {
        return this.sessionProxy.selectCall(callable);
    }

    /**
     * session代理
     */
    private class SessionInvocationHandler implements InvocationHandler {
        private SessionInvocationHandler() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Session session = SessionUtils.getSession(SessionTemplate.this.sessionFactory);
            Object result = method.invoke(session, args);
            //判断当前线程绑定的session的事务没有被激活 执行事务提交
            if (!SessionUtils.isSessionTransactional(session, SessionTemplate.this.sessionFactory)) {
                session.commit();
            }
            // 关闭 session
            if (session != null) {
                SessionUtils.closeSession(session, SessionTemplate.this.sessionFactory);
            }

            return result;
        }
    }


    /**
     * mapper代理
     */
    private class MapperInvocationHandler extends MapperProxyInvocationHandler {
        /**
         * 代理控制器有参构造
         *
         * @param session              session实例
         * @param methodInfoRepository mapperMethodInfo集合
         * @param mapperInterface      被代理接口的Class
         */
        private MapperInvocationHandler(Session session, MapperMethodInfoRepository methodInfoRepository, Class<?> mapperInterface) {
            super(session, methodInfoRepository, mapperInterface);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                return super.invoke(proxy, method, args);
            } catch (Throwable throwable) {
                Throwable unwrapped = ExceptionUtil.unwrapThrowable(throwable);
                if (unwrapped instanceof DarlingException) {
                    Throwable translated = SessionTemplate.this.darlingPersistenceExceptionTranslator.translateExceptionIfPossible((DarlingException)unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }

                throw unwrapped;
            }
        }
    }
}
